package conMgr.data.tests;

import static org.junit.Assert.*;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.junit.After;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import conMgr.data.XmlManager;
import conMgr.model.Status;
import conMgr.model.User;
import conMgr.model.UserType;
import conMgr.prog.tests.MockErrorListener;

public class XmlManagerTests
{
	private final MockErrorListener listener;
	
	private final XmlManager xml;
	
	public XmlManagerTests()
	{
		listener = new MockErrorListener();
		
		xml = new XmlManager();
		xml.addErrorListener(listener);
	}
	
	@After
	public void cleanUpAfterTest()
	{
		listener.errors().clear();
	}
	
	private Element getElement(final String xmlString)
		throws ParserConfigurationException, SAXException, IOException
	{
		final DocumentBuilderFactory factory =
			DocumentBuilderFactory.newInstance();
		final DocumentBuilder builder = factory.newDocumentBuilder();
		final InputSource source = new InputSource();
		final StringReader reader = new StringReader(xmlString);
		
		source.setCharacterStream(reader);
		final Document document = builder.parse(source);
		document.normalize();
		
		return document.getDocumentElement();
	}
	
	@Test
	public void testAppendNode()
		throws
			ParserConfigurationException,
			SAXException,
			IOException,
			TransformerException
	{
		final Element element = getElement("<node/>");
		xml.appendNode(element, "child");
		assertEquals("<node><child/></node>", toXml(element));
	}

	@Test
	public void testAppendValueElementStringDate()
		throws
			ParserConfigurationException,
			SAXException,
			IOException,
			TransformerException
	{
		final Element element = getElement("<node/>");
		Calendar calendar = Calendar.getInstance();
		calendar.set(1983, Calendar.SEPTEMBER, 30);
		xml.appendValue(element, "value", calendar.getTime());
		assertEquals("<node value=\"09/30/1983\"/>", toXml(element));
	}

	@Test
	public void testAppendValueElementStringInteger()
		throws
			ParserConfigurationException,
			SAXException,
			IOException,
			TransformerException
	{
		final Element element = getElement("<node/>");
		xml.appendValue(element, "value", 42);
		assertEquals("<node value=\"42\"/>", toXml(element));
	}

	@Test
	public void testAppendValueElementStringListOfInteger()
		throws
			ParserConfigurationException,
			SAXException,
			IOException,
			TransformerException
	{
		Element element = getElement("<node/>");
		final List<Integer> list = new LinkedList<Integer>();
		xml.appendValue(element, "value", list);
		assertEquals("<node value=\"\"/>", toXml(element));
		
		element = getElement("<node/>");
		list.add(4);
		list.add(3);
		list.add(2);
		list.add(1);
		xml.appendValue(element, "value", list);
		assertEquals("<node value=\"4,3,2,1\"/>", toXml(element));
	}

	@Test
	public void testAppendValueElementStringString()
		throws
			ParserConfigurationException,
			SAXException,
			IOException,
			TransformerException
	{
		final Element element = getElement("<node/>");
		xml.appendValue(element, "value", "AbCdEf");
		assertEquals("<node value=\"AbCdEf\"/>", toXml(element));
	}

	@Test
	public void testAppendValueElementStringTMapOfTInteger()
		throws
			ParserConfigurationException,
			SAXException,
			IOException,
			TransformerException
	{
		final Map<User, Integer> map = new HashMap<User, Integer>();
		final User nolan =
			new User("Ryan, Nolan", "nryan@mlb.com", UserType.ADMIN); 
		final User randy =
			new User("Johnson, Randy", "rjohnson@mlb.com", UserType.ADMIN); 
		final User roger =
			new User("Clemens, Roger", "rclemens@mlb.com", UserType.ADMIN);
		final User steve =
			new User("Carlton, Steve", "scarlton@mlb.com", UserType.ADMIN);
		map.put(nolan, 5714);
		map.put(randy, 4875);
		map.put(roger, 4672);
		
		Element element = getElement("<node/>");
		boolean result = xml.appendValue(element, "user", steve, map);
		assertFalse(result);
		assertEquals(1, listener.errors().size());
		assertEquals("Internal error.", listener.errors().get(0).description);
		assertNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node/>");
		result = xml.appendValue(element, "user", randy, map);
		assertTrue(result);
		assertEquals("<node user=\"4875\"/>", toXml(element));
	}

	@Test
	public void testAppendValueElementStringEnumOfT()
		throws
			ParserConfigurationException,
			SAXException,
			IOException,
			TransformerException
	{
		final Element element = getElement("<node/>");
		xml.appendValue(element, "value", Status.ACCEPTED);
		assertEquals("<node value=\"ACCEPTED\"/>", toXml(element));
	}
	
	@Test
	public void testGetDate()
		throws ParserConfigurationException, SAXException, IOException
	{
		Element element = getElement("<node badName=\"09/30/1983\"/>");
		Date result = xml.getDate(element, "value", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getDate(element, "value", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot find 'value' attribute on 'node' node.",
			listener.errors().get(0).description
		);
		assertNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node value=\"09/3a/1983\"/>");
		result = xml.getDate(element, "value", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getDate(element, "value", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot parse 'value' value '09/3a/1983' into a date.",
			listener.errors().get(0).description
		);
		assertNotNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node value=\"09/30/1983\"/>");
		result = xml.getDate(element, "value", true);
		assertNotNull(result);
		assertEquals(0, listener.errors().size());
		final Calendar calendar = Calendar.getInstance();
		calendar.setTime(result);
		assertEquals(1983, calendar.get(Calendar.YEAR));
		assertEquals(Calendar.SEPTEMBER, calendar.get(Calendar.MONTH));
		assertEquals(30, calendar.get(Calendar.DAY_OF_MONTH));
	}

	@Test
	public void testGetElements()
		throws ParserConfigurationException, SAXException, IOException
	{
		Element element = getElement("<xml><c><d/><d/><d/></c></xml>");
		List<Element> result = xml.getElements(element, "a", "b");
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Found no <a> element in XML file. Cannot load data.",
			listener.errors().get(0).description
		);
		assertNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<xml><a><b/><b/><b/></a><a><b/><b/></a></xml>");
		result = xml.getElements(element, "a", "b");
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Found multiple <a> elements in XML file. Only loading the first " +
			"one...",
			listener.errors().get(0).description
		);
		assertNull(listener.errors().get(0).e);
		assertNotNull(result);
		assertEquals(3, result.size());
		assertEquals("b", result.get(0).getNodeName());
		assertEquals("b", result.get(1).getNodeName());
		assertEquals("b", result.get(2).getNodeName());
		listener.errors().clear();
		
		element = getElement("<xml><a><b/><b/><c/><b/></a></xml>");
		result = xml.getElements(element, "a", "b");
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Found an unknown element in <a> in XML file.",
			listener.errors().get(0).description
		);
		assertNull(listener.errors().get(0).e);
	}

	@Test
	public void testGetEntity()
		throws ParserConfigurationException, SAXException, IOException
	{
		final Map<Integer, User> map = new HashMap<Integer, User>();
		final User nolan =
			new User("Ryan, Nolan", "nryan@mlb.com", UserType.ADMIN); 
		final User randy =
			new User("Johnson, Randy", "rjohnson@mlb.com", UserType.ADMIN); 
		final User roger =
			new User("Clemens, Roger", "rclemens@mlb.com", UserType.ADMIN); 
		map.put(5714, nolan);
		map.put(4875, randy);
		map.put(4672, roger);
		
		Element element = getElement("<node badName=\"4875\"/>");
		User result = xml.getEntity(element, map, "user", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getEntity(element, map, "user", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot find 'user' attribute on 'node' node.",
			listener.errors().get(0).description
		);
		assertNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node user=\"4875a\"/>");
		result = xml.getEntity(element, map, "value", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getEntity(element, map,  "user", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot parse 'user' value '4875a' into an integer.",
			listener.errors().get(0).description
		);
		assertNotNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node user=\"4874\"/>");
		result = xml.getEntity(element, map, "user", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getEntity(element, map,  "user", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot find entity with ID '4874'.",
			listener.errors().get(0).description
		);
		assertNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node value=\"4875\"/>");
		result = xml.getEntity(element, map,  "value", true);
		assertNotNull(result);
		assertEquals(0, listener.errors().size());
		assertEquals(randy, result);
	}

	@Test
	public void testGetInt()
		throws ParserConfigurationException, SAXException, IOException
	{
		Element element = getElement("<node badName=\"42\"/>");
		Integer result = xml.getInt(element, "value", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getInt(element, "value", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot find 'value' attribute on 'node' node.",
			listener.errors().get(0).description
		);
		assertNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node value=\"4a\"/>");
		result = xml.getInt(element, "value", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getInt(element, "value", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot parse 'value' value '4a' into an integer.",
			listener.errors().get(0).description
		);
		assertNotNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node value=\"42\"/>");
		result = xml.getInt(element, "value", true);
		assertNotNull(result);
		assertEquals(0, listener.errors().size());
		assertEquals(42, result.intValue());
	}

	@Test
	public void testGetIntArray()
		throws ParserConfigurationException, SAXException, IOException
	{
		Element element = getElement("<node badName=\"1,2,3,4\"/>");
		List<Integer> result = xml.getIntArray(element, "values", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getIntArray(element, "values", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot find 'values' attribute on 'node' node.",
			listener.errors().get(0).description
		);
		assertNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node values=\"1,2,a,4\"/>");
		result = xml.getIntArray(element, "values", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getIntArray(element, "values", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot parse 'values' value 'a' into an integer.",
			listener.errors().get(0).description
		);
		assertNotNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node values=\"5,6,7,8\"/>");
		result = xml.getIntArray(element, "values", true);
		assertNotNull(result);
		assertEquals(0, listener.errors().size());
		assertEquals(4, result.size());
		assertEquals(5, result.get(0).intValue());
		assertEquals(6, result.get(1).intValue());
		assertEquals(7, result.get(2).intValue());
		assertEquals(8, result.get(3).intValue());
	}

	@Test
	public void testGetStatus()
		throws ParserConfigurationException, SAXException, IOException
	{
		Element element = getElement("<node badName=\"ACCEPTED\"/>");
		Status result = xml.getStatus(element, "value", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getStatus(element, "value", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot find 'value' attribute on 'node' node.",
			listener.errors().get(0).description
		);
		assertNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node value=\"unACCEPTED\"/>");
		result = xml.getStatus(element, "value", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getStatus(element, "value", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot parse a Status from 'unACCEPTED'.",
			listener.errors().get(0).description
		);
		assertNotNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node value=\"ACCEPTED\"/>");
		result = xml.getStatus(element, "value", true);
		assertNotNull(result);
		assertEquals(0, listener.errors().size());
		assertEquals(Status.ACCEPTED, result);
	}

	@Test
	public void testGetString()
		throws ParserConfigurationException, SAXException, IOException
	{
		Element element = getElement("<node badName=\"AbCdEf\"/>");
		String result = xml.getString(element, "value", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getString(element, "value", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot find 'value' attribute on 'node' node.",
			listener.errors().get(0).description
		);
		assertNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node value=\"AbCdEf\"/>");
		result = xml.getString(element, "value", true);
		assertNotNull(result);
		assertEquals(0, listener.errors().size());
		assertEquals("AbCdEf", result);
	}

	@Test
	public void testGetType()
		throws ParserConfigurationException, SAXException, IOException
	{
		Element element = getElement("<node badName=\"ADMIN\"/>");
		UserType result = xml.getType(element, "value", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getType(element, "value", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot find 'value' attribute on 'node' node.",
			listener.errors().get(0).description
		);
		assertNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node value=\"unADMIN\"/>");
		result = xml.getType(element, "value", false);
		assertNull(result);
		assertEquals(0, listener.errors().size());
		
		result = xml.getType(element, "value", true);
		assertNull(result);
		assertEquals(1, listener.errors().size());
		assertEquals
		(
			"Cannot parse a UserType from 'unADMIN'.",
			listener.errors().get(0).description
		);
		assertNotNull(listener.errors().get(0).e);
		listener.errors().clear();
		
		element = getElement("<node value=\"ADMIN\"/>");
		result = xml.getType(element, "value", true);
		assertNotNull(result);
		assertEquals(0, listener.errors().size());
		assertEquals(UserType.ADMIN, result);
	}
	
	private String toXml(final Element element)
		throws TransformerException
	{
		element.normalize();
		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = factory.newTransformer();
		StringWriter writer = new StringWriter();
		StreamResult result = new StreamResult(writer);
		transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
		transformer.transform(new DOMSource(element), result);
		return writer.toString();
	}
}
